Package org.python.pydev.outline

Source Code of org.python.pydev.outline.PyOutlinePage

/**
* Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Author: atotic
* Author: fabioz
*
* Created: Jul 10, 2003
*/
package org.python.pydev.outline;

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.part.IShowInTarget;
import org.eclipse.ui.part.ShowInContext;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.python.pydev.core.ExtensionHelper;
import org.python.pydev.core.bundle.ImageCache;
import org.python.pydev.core.callbacks.CallbackWithListeners;
import org.python.pydev.core.callbacks.ICallbackWithListeners;
import org.python.pydev.core.docutils.PySelection;
import org.python.pydev.core.log.Log;
import org.python.pydev.editor.PyEdit;
import org.python.pydev.parser.ErrorDescription;
import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.ui.IViewCreatedObserver;
import org.python.pydev.ui.IViewWithControls;
import org.python.pydev.ui.UIConstants;


/**
* Outline page, displays the structure of the document in the editor window.
*
* Partition outlining:<p>
* PyDocumentProvider already partitions the document into strings/comments/other<p>
* RawPartition is the simplest outline that shows this "raw" document partitioning<p>
* raw partition was only used as an example, not useful in production<p>
*
* @note: tests for the outline page are not directly for the outline page, but for its model,
* based on ParsedItems.
**/
@SuppressWarnings({ "rawtypes", "unchecked" })
public class PyOutlinePage extends ContentOutlinePageWithFilter implements IShowInTarget, IAdaptable, IViewWithControls {

    PyEdit editorView;
    IDocument document;
    IOutlineModel model;
    ImageCache imageCache;

    // listeners to rawPartition
    ISelectionChangedListener selectionListener;

    private OutlineLinkWithEditorAction linkWithEditor;
    public final ICallbackWithListeners onControlCreated = new CallbackWithListeners();
    public final ICallbackWithListeners onControlDisposed = new CallbackWithListeners();
    private List createdCallbacksForControls;

    public PyOutlinePage(PyEdit editorView) {
        super();
        List<IViewCreatedObserver> participants = ExtensionHelper
                .getParticipants(ExtensionHelper.PYDEV_VIEW_CREATED_OBSERVER);
        for (IViewCreatedObserver iViewCreatedObserver : participants) {
            iViewCreatedObserver.notifyViewCreated(this);
        }
        this.editorView = editorView;
        imageCache = new ImageCache(PydevPlugin.getDefault().getBundle().getEntry("/"));
    }

    public void dispose() {
        onControlDisposed.call(getTreeViewer());
        if (createdCallbacksForControls != null) {
            for (Object o : createdCallbacksForControls) {
                onControlDisposed.call(o);
            }
            createdCallbacksForControls = null;
        }

        if (model != null) {
            model.dispose();
            model = null;
        }
        if (selectionListener != null) {
            removeSelectionChangedListener(selectionListener);
        }
        if (imageCache != null) {
            imageCache.dispose();
        }
        if (linkWithEditor != null) {
            linkWithEditor.dispose();
            linkWithEditor = null;
        }
        super.dispose();
    }

    /**
     * Parsed partition creates an outline that shows imports/classes/methods
     */
    private void createParsedOutline() {
        final TreeViewer tree = getTreeViewer();
        IDocumentProvider provider = editorView.getDocumentProvider();
        document = provider.getDocument(editorView.getEditorInput());
        model = getParsedModel();
        tree.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
        tree.setContentProvider(new ParsedContentProvider());
        tree.setLabelProvider(new ParsedLabelProvider(imageCache));
        tree.setInput(model.getRoot());
    }

    /**
     *
     * @return the parsed model, so that it can be used elsewhere (in navigation)
     */
    public ParsedModel getParsedModel() {
        return new ParsedModel(this, editorView);
    }

    public boolean isDisposed() {
        return getTreeViewer().getTree().isDisposed();
    }

    /**
     * called when model has structural changes, refreshes all items underneath
     * @param items: items to refresh, or null for the whole tree
     * tries to preserve the scrolling
     */
    public void refreshItems(Object[] items) {
        try {
            unlinkAll();
            TreeViewer viewer = getTreeViewer();
            if (viewer != null) {
                Tree treeWidget = viewer.getTree();
                if (isDisposed()) {
                    return;
                }

                ScrollBar bar = treeWidget.getVerticalBar();
                int barPosition = 0;
                if (bar != null) {
                    barPosition = bar.getSelection();
                }
                if (items == null) {
                    if (isDisposed()) {
                        return;
                    }
                    viewer.refresh();

                } else {
                    if (isDisposed()) {
                        return;
                    }
                    for (int i = 0; i < items.length; i++) {
                        viewer.refresh(items[i]);
                    }
                }

                if (barPosition != 0) {
                    bar.setSelection(Math.min(bar.getMaximum(), barPosition));
                }
            }
        } catch (Throwable e) {
            //things may be disposed...
            Log.log(e);
        } finally {
            relinkAll();
        }
    }

    /**
     * called when a single item changes
     */
    public void updateItems(Object[] items) {
        try {
            unlinkAll();
            if (isDisposed()) {
                return;
            }
            TreeViewer tree = getTreeViewer();
            if (tree != null) {
                tree.update(items, null);
            }
        } finally {
            relinkAll();
        }
    }

    /**
     * @return the preference store we should use
     */
    /*package*/IPreferenceStore getStore() {
        return PydevPlugin.getDefault().getPreferenceStore();
    }

    @Override
    public TreeViewer getTreeViewer() {
        return super.getTreeViewer();
    }

    private void createActions() {
        linkWithEditor = new OutlineLinkWithEditorAction(this, imageCache);

        //---- Collapse all
        Action collapseAll = new Action("Collapse all", IAction.AS_PUSH_BUTTON) {
            public void run() {
                getTreeViewer().collapseAll();
            }
        };

        //---- Expand all
        Action expandAll = new Action("Expand all", IAction.AS_PUSH_BUTTON) {
            public void run() {
                getTreeViewer().expandAll();
            }
        };

        collapseAll.setImageDescriptor(imageCache.getDescriptor(UIConstants.COLLAPSE_ALL));
        expandAll.setImageDescriptor(imageCache.getDescriptor(UIConstants.EXPAND_ALL));

        // Add actions to the toolbar
        IActionBars actionBars = getSite().getActionBars();
        IToolBarManager toolbarManager = actionBars.getToolBarManager();

        toolbarManager.add(new OutlineSortByNameAction(this, imageCache));
        toolbarManager.add(collapseAll);
        toolbarManager.add(expandAll);

        IMenuManager menuManager = actionBars.getMenuManager();
        menuManager.add(linkWithEditor);
        menuManager.add(new OutlineHideCommentsAction(this, imageCache));
        menuManager.add(new OutlineHideImportsAction(this, imageCache));
        menuManager.add(new OutlineHideMagicObjectsAction(this, imageCache));
        menuManager.add(new OutlineHideFieldsAction(this, imageCache));
        menuManager.add(new OutlineHideNonPublicMembersAction(this, imageCache));
        menuManager.add(new OutlineHideStaticMethodsAction(this, imageCache));
    }

    /**
     * create the outline view widgets
     */
    public void createControl(Composite parent) {
        super.createControl(parent); // this creates a tree viewer
        try {
            createParsedOutline();
            // selecting an item in the outline scrolls the document
            selectionListener = new ISelectionChangedListener() {

                public void selectionChanged(SelectionChangedEvent event) {
                    if (linkWithEditor == null) {
                        return;
                    }
                    try {
                        unlinkAll();
                        StructuredSelection sel = (StructuredSelection) event.getSelection();

                        boolean alreadySelected = false;
                        if (sel.size() == 1) { // only sync the editing view if it is a single-selection
                            ParsedItem firstElement = (ParsedItem) sel.getFirstElement();
                            ErrorDescription errorDesc = firstElement.getErrorDesc();

                            //select the error
                            if (errorDesc != null && errorDesc.message != null) {
                                int len = errorDesc.errorEnd - errorDesc.errorStart;
                                editorView.setSelection(errorDesc.errorStart, len);
                                alreadySelected = true;
                            }
                        }
                        if (!alreadySelected) {
                            SimpleNode[] node = model.getSelectionPosition(sel);
                            editorView.revealModelNodes(node);
                        }
                    } finally {
                        relinkAll();
                    }
                }
            };
            addSelectionChangedListener(selectionListener);
            createActions();

            //OK, instead of using the default selection engine, we recreate it only to handle mouse
            //and key events directly, because it seems that sometimes, SWT creates spurious select events
            //when those shouldn't be created, and there's also a risk of creating loops with the selection,
            //as when one selection arrives when we're linked, we have to perform a selection and doing that
            //selection could in turn trigger a new selection, so, we remove that treatment and only start
            //selections from interactions the user did.
            //see: Cursor jumps to method definition when an error is detected
            //https://sourceforge.net/tracker2/?func=detail&aid=2057092&group_id=85796&atid=577329
            TreeViewer treeViewer = getTreeViewer();
            treeViewer.removeSelectionChangedListener(this);
            Tree tree = treeViewer.getTree();

            tree.addMouseListener(new MouseListener() {

                public void mouseDoubleClick(MouseEvent e) {
                    tryToMakeSelection();
                }

                public void mouseDown(MouseEvent e) {
                }

                public void mouseUp(MouseEvent e) {
                    tryToMakeSelection();
                }
            });

            tree.addKeyListener(new KeyListener() {

                public void keyPressed(KeyEvent e) {
                }

                public void keyReleased(KeyEvent e) {
                    if (e.keyCode == SWT.ARROW_UP || e.keyCode == SWT.ARROW_DOWN) {
                        tryToMakeSelection();
                    }
                }
            });

            onControlCreated.call(getTreeViewer());
            createdCallbacksForControls = callRecursively(onControlCreated, filter, new ArrayList());
        } catch (Throwable e) {
            Log.log(e);
        }
    }

    /**
     * Calls the callback with the composite c and all of its children (recursively).
     */
    private List callRecursively(ICallbackWithListeners callback, Composite c, ArrayList controls) {
        try {
            for (Control child : c.getChildren()) {
                if (child instanceof Composite) {
                    callRecursively(callback, (Composite) child, controls);
                }
                controls.add(child);
                callback.call(child);
            }
        } catch (Throwable e) {
            Log.log(e);
        }
        return controls;
    }

    public boolean show(ShowInContext context) {
        linkWithEditor.doLinkOutlinePosition(this.editorView, this, new PySelection(this.editorView));
        return true;
    }

    public Object getAdapter(Class adapter) {
        if (adapter == IShowInTarget.class) {
            return this;
        }
        return null;
    }

    @Override
    public void selectionChanged(SelectionChangedEvent event) {
        super.selectionChanged(event);
    }

    /**
     * Used to hold a link level to know when it should be unlinked or relinked, as calls can be 'cascaded'
     */
    private int linkLevel = 1;

    /**
     * Used for locking link/unlink access.
     */
    private Object lock = new Object();

    /**
     * Stops listening to changes (the linkLevel is used so that multiple unlinks can be called and later
     * multiple relinks should be used)
     */
    void unlinkAll() {
        synchronized (lock) {
            linkLevel--;
            if (linkLevel == 0) {
                removeSelectionChangedListener(selectionListener);
                if (linkWithEditor != null) {
                    linkWithEditor.unlink();
                }
            }
        }
    }

    /**
     * Starts listening to changes again if the number of relinks matches the number of unlinks
     */
    void relinkAll() {
        synchronized (lock) {
            linkLevel++;
            if (linkLevel == 1) {
                addSelectionChangedListener(selectionListener);
                if (linkWithEditor != null) {
                    linkWithEditor.relink();
                }
            } else if (linkLevel > 1) {
                throw new RuntimeException("Error: relinking without unlinking 1st");
            }
        }
    }

    /**
     * Creates an event of a selection change if it's possible to do so (otherwise returns null)
     */
    private SelectionChangedEvent createSelectionEvent() {
        SelectionChangedEvent event = null;
        ISelection selection = getSelection();
        if (selection instanceof IStructuredSelection) {
            IStructuredSelection s = (IStructuredSelection) selection;
            if (s.iterator().hasNext()) {
                //only make the selection if there's some item selected
                event = new SelectionChangedEvent(getTreeViewer(), selection);
            }
        }
        return event;
    }

    /**
     * Tries to trigger a selection changed event (if a selection is available for doing so)
     */
    private void tryToMakeSelection() {
        SelectionChangedEvent event = createSelectionEvent();
        if (event != null) {
            selectionChanged(event);
        }
    }

    public ICallbackWithListeners getOnControlCreated() {
        return onControlCreated;
    }

    public ICallbackWithListeners getOnControlDisposed() {
        return onControlDisposed;
    }

}
TOP

Related Classes of org.python.pydev.outline.PyOutlinePage

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.